<?php
namespace gnomephp\input;

class DataField{
	private $value;

	private $keyName;


	private $messages=array();
	private $rules=array();

	public function __construct($value, $keyName=null){
		$this->setValue($value);
		$this->keyName = $keyName;
	}

	/**
	 * Returns the value of the field.
	 */
	public function __toString(){
		return $this->value;
	}

	/**
	 * Sets field to a specific value.
	 * @param mixed $value
	 */
	public function setValue($value){
		$this->value = $value;
	}



	/**
	 * Alias of __toString() method.
	 */
	public function getData(){
		return $this->__toString();
	}


	/**
	 * Validate runs the validation rules applies to this DataField.
	 * Throws DataFieldValidException if the field
	 * @throws DataFieldValidException
	 */
	public function validate($keyName=null){
		$errors = array();
		// try each rule function
		foreach ($this->rules as $rule => $function) {
			if (is_callable($function)) {
				if ($function($this->value) === FALSE) {
					$errors[] = sprintf($this->messages[$rule], $keyName ? $keyName : $this->keyName);
				}
			}
		}

		if (count($errors) > 0){
			throw new DataFieldValidException($errors);
		}

		return $this;
	}

	/// ----- Validation rules goes here -----
	/**
	 * email
	 * @param string $message
	 * @return FormValidator
	 */

	public function email($message='')
	{
		$message = ( empty ($message) ) ? '%s is an invalid email address.' : $message;
		$this->addValidaitonRule(__FUNCTION__, function($email) {
			return ( filter_var($email, FILTER_VALIDATE_EMAIL) === FALSE ) ? FALSE : TRUE;
		}, $message);
		return $this;
	}
	/**
	 * required
	 * @param string $message
	 * @return FormValidator
	 */

	public function required($message='')
	{
		$message = ( empty ($message) ) ? '%s is required.' : $message;
		$this->addValidaitonRule(__FUNCTION__, function($string) {
			return ( empty ($string) ) ? FALSE : TRUE;
		}, $message);
		return $this;
	}

	/**
	 * Field must start with a specific substring.
	 *
	 * @param string $sub
	 * @param string $message
	 * @return FormValidator
	 */
	public function startsWith($sub, $message = null) {
		$message = ( empty ($message) ) ? '%s must start with .'.$sub.'.' : $message;
		$this->set_rule(__FUNCTION__, function($string)  use ($sub){
			return (strlen($string) === 0 || substr($string, 0, strlen($sub)) === $sub);
		}, $message);
		return $this;
	}

	/**
	 * Field has to be valid internet address.
	 *
	 * @param string $message
	 * @return FormValidator
	 */
	public function url($message = null) {
		$message = ( empty ($message) ) ? '%s must be a valid URL.' : $message;
		$this->set_rule(__FUNCTION__, function($string) {
			return (strlen(trim($string)) === 0 || filter_var($string, FILTER_VALIDATE_URL)) ? TRUE : FALSE;
		}, $message);
		return $this;
	}

	/**
	 * Field must contain a valid float value.
	 *
	 * @param string $message
	 * @return FormValidator
	 */
	public function float($message = null) {
		$message = ( empty ($message) ) ? '%s must be a valid float value.' : $message;
		$this->set_rule(__FUNCTION__, function($string) {
			return (filter_var($string, FILTER_VALIDATE_FLOAT) === FALSE) ? FALSE : TRUE;
		}, $message);
		return $this;
	}

	/**
	 * Field has to be a valid date.
	 *
	 * @param string $message
	 * @return FormValidator
	 */
	public function date($format='YYYY-MM-DD', $message = null) {
		$message = ( empty ($message) ) ? '%s must be a valid date format ('.$format.')' : $message;
		if (empty($format)) {
			$format = self::getDefaultDateFormat();
		}

		$this->set_rule(__FUNCTION__, function($string) use ($format) {
			return DataField::validateDate($string, $format);
		}, $message);
		return $this;
	}

	/**
	 * Field has to be a valid credit card number format.
	 *
	 * @see https://github.com/funkatron/inspekt/blob/master/Inspekt.php
	 * @param string $message
	 * @return FormValidator
	 */
	public function ccnum($message = null) {
		$message = ( empty ($message) ) ? '%s must be a valid credit card number.' : $message;
		$this->set_rule(__FUNCTION__, function($value) {
			$value = str_replace(' ', '', $value);
			$length = strlen($value);

			if ($length < 13 || $length > 19) {
				return FALSE;
			}

			$sum = 0;
			$weight = 2;

			for ($i = $length - 2; $i >= 0; $i--) {
				$digit = $weight * $value[$i];
				$sum += floor($digit / 10) + $digit % 10;
				$weight = $weight % 2 + 1;
			}

			$mod = (10 - $sum % 10) % 10;

			return ($mod == $value[$length - 1]);
		}, $message);
		return $this;
	}
	/**
	 * Field must contain a valid integer value.
	 *
	 * @param string $message
	 * @return FormValidator
	 */
	public function integer($message = null) {
		$message = ( empty ($message) ) ? '%s must be a valid integer.' : $message;
		$this->set_rule(__FUNCTION__, function($string) {
			return (filter_var($string, FILTER_VALIDATE_INT) === FALSE) ? FALSE : TRUE;
		}, $message);
		return $this;
	}

	/**
	 * Every character in field, if completed, must be a digit.
	 * This is just like integer(), except there is no upper limit.
	 *
	 * @param string $message
	 * @return FormValidator
	 */
	public function digits($message = null) {
		$message = ( empty ($message) ) ? '%s must be a valid digit.' : $message;
		$this->set_rule(__FUNCTION__, function($value) {
			return (strlen($value) === 0 || ctype_digit((string) $value));
		}, $message);
		return $this;
	}
	/**
	 *
	 * Matches a regexp pattern.
	 */
	public function regexpMatch($pattern, $message = null){
		$message = ( empty ($message) ) ? '%s does not match the requiered pattern.' : $message;
		$this->set_rule(__FUNCTION__, function($value, $pattern) {
			if (!$value)return true;
				
			return preg_match($pattern, $value) ? TRUE : FALSE;
		}, $message, array($pattern));
		return $this;
	}

	/**
	 * Field must be a number greater than [or equal to] X.
	 *
	 * @param numeric $limit
	 * @param bool $include Whether to include limit value.
	 * @param string $message
	 * @return FormValidator
	 */
	public function min($limit, $include = TRUE, $message = null) {
		$message = ( empty ($message) ) ? '%s must be the value for atleast '.$limit.'.' : $message;
		$this->set_rule(__FUNCTION__, function($value) use ($limit, $include) {
			if (strlen($value) === 0) {
				return TRUE;
			}

			$value = (float) $value;
			$inc = (bool) $include;

			return ($value > $limit || ($inc === TRUE && $value === $limit));
		}, $message);
		return $this;
	}

	/**
	 * Field must be a number greater than [or equal to] X.
	 *
	 * @param numeric $limit
	 * @param bool $include Whether to include limit value.
	 * @param string $message
	 * @return FormValidator
	 */
	public function max($limit, $include = TRUE, $message = null) {
		$message = ( empty ($message) ) ? '%s can be the maximum value of '.$limit.'.' : $message;
		$this->set_rule(__FUNCTION__, function($value) use ($limit, $include) {
			if (strlen($value) === 0) {
				return TRUE;
			}

			$value = (float) $value;
			$limit = (float) $limit;
			$inc = (bool) $include;

			return ($value < $limit || ($inc === TRUE && $value === $limit));
		}, $message);
		return $this;
	}

	/**
	 * Field must be a number between X and Y.
	 *
	 * @param numeric $min
	 * @param numeric $max
	 * @param bool $include Whether to include limit value.
	 * @param string $message
	 * @return FormValidator
	 */
	public function between($min, $max, $include = TRUE, $message = null) {
		$message = ( empty ($message) ) ? '%s must be between '.$min.' and '.$max.'.' : $message;
		$this->min($min, $include, $message)->max($max, $include, $message);
		return $this;
	}

	/**
	 * numbersonly
	 * @param string $message
	 * @return FormValidator
	 */
	public function numeric($message='')
	{
		$message = ( empty ($message) ) ? '%s must consist of numbers only.' : $message;
		$this->addValidaitonRule(__FUNCTION__, function($string) {
			return ( preg_match('/^[-]?[0-9.]+$/', $string) ) ? TRUE : FALSE;
		}, $message);
		return $this;
	}

	/**
	 *
	 * @param int $len
	 * @param string $message
	 * @return FormValidator
	 */
	public function minLength($minlen, $message='')
	{
		$message = ( empty ($message) ) ? '%s must be at least ' . $minlen . ' characters or longer.' : $message;
		$this->addValidaitonRule(__FUNCTION__, function($string) use ($minlen) {
			return ( strlen($string) < $minlen ) ? FALSE : TRUE;
		}, $message);
		return $this;
	}

	/**
	 *
	 * @param int $len
	 * @param string $message
	 * @return FormValidator
	 */
	public function maxLength($maxlen, $message='')
	{
		$message = ( empty ($message) ) ? '%s must be no longer than ' . $maxlen . ' characters.' : $message;
		$this->addValidaitonRule(__FUNCTION__, function($string) use ($maxlen) {
			return ( strlen($string) > $maxlen ) ? FALSE : TRUE;
		}, $message);
		return $this;
	}

	/**
	 *
	 * @param int $len
	 * @param string $message
	 * @return FormValidator
	 */
	public function length($len, $message='')
	{
		$message = ( empty ($message) ) ? '%s must be exactly ' . $len . ' characters in length.' : $message;
		$this->addValidaitonRule(__FUNCTION__, function($string) use ($len) {
			return ( strlen($string) == $len ) ? TRUE : FALSE;
		}, $message);
		return $this;
	}

	/**
	 *
	 * @param string $field
	 * @param string $label
	 * @param string $message
	 * @return FormValidator
	 */
	public function matches($field, $label, $message='')
	{
		$message = ( empty ($message) ) ? '%s must match ' . $label . '.' : $message;

		$matchvalue = $field;

		$this->addValidaitonRule(__FUNCTION__, function($string) use ($matchvalue) {
			return ( (string)$matchvalue == (string)$string ) ? TRUE : FALSE;
		}, $message);
		return $this;
	}

	/**
	 *
	 * @param string $field
	 * @param string $label
	 * @param string $message
	 * @return FormValidator
	 */
	public function notMatches($field, $label, $message='')
	{
		$message = ( empty ($message) ) ? '%s must not match ' . $label . '.' : $message;

		$matchvalue = $field;

		$this->addValidaitonRule(__FUNCTION__, function($string) use ($matchvalue) {
			return ( (string)$matchvalue == (string)$string ) ? FALSE : TRUE;
		}, $message);
		return $this;
	}
	
	/**
	 * Field has to be a date later than or equal to X.
	 *
	 * @param string|integer $date Limit date.
	 * @param string $format Date format.
	 * @param string $message
	 * @return FormValidator
	 */
	public function maxDate($date = 0, $format = null, $message = null) {
		$message = ( empty ($message) ) ? '%s must be between '.$date.'.' : $message;
		if (empty($format)) {
			$format = DataField::getDefaultDateFormat();
		}
		if (is_numeric($date)) {
			$date = new \DateTime($date . ' days'); // Days difference from today
		} else {
			$fieldValue = $this->getval($date);
			$date = ($fieldValue == FALSE) ? $date : $fieldValue;

			$date = \DateTime::createFromFormat($format, $date);
		}

		$this->set_rule(__FUNCTION__, function($string) use ($format, $date) {
			$limitDate = $date;

			return ($limitDate < \DateTime::createFromFormat($format, $string)) ? FALSE : TRUE;
		}, $message);
		return $this;
	}
	
	/**
	 * Field has to be a date later than or equal to X.
	 *
	 * @param string $message
	 * @return FormValidator
	 */
	public function minDate($date = 0, $format = null, $message = null) {
		if (empty($format)) {
			$format = self::getDefaultDateFormat();
		}
		if (is_numeric($date)) {
			$date = new \DateTime($date . ' days'); // Days difference from today
		} else {
			$fieldValue = $this->getval($date);
			$date = ($fieldValue == FALSE) ? $date : $fieldValue;

			$date = \DateTime::createFromFormat($format, $date);
		}

		$this->set_rule(__FUNCTION__, function($string) use ($date, $format){
			$limitDate = $date;

			return ($limitDate > \DateTime::createFromFormat($format, $string)) ? FALSE : TRUE;
		}, $message);
		return $this;
	}
	

	/**
	 * Field has to be valid IP address.
	 *
	 * @param string $message
	 * @return FormValidator
	 */
	public function ip($message = null) {
		$message = ( empty ($message) ) ? '%s is not a valid ip-address.' : $message;
		$this->set_rule(__FUNCTION__, function($string) {
			return (strlen(trim($string)) === 0 || filter_var($string, FILTER_VALIDATE_IP)) ? TRUE : FALSE;
		}, $message);
		return $this;
	}
	

	/// ----- Stop validaiton rules....
	
	
	/**
	 * addValidaitonRule
	 * @param string $rule
	 * @param closure $function
	 * @param string $message
	 */

	public function addValidaitonRule($rule, $function, $message='') {
		if (is_callable($function)) {
			$this->rules[$rule] = $function;
			if (!empty($message)) {
				$this->messages[$rule] = $message;
			}
		}
		return $this;
	}

	/**
	 *
	 * Validate a date
	 * see http://www.phpro.org/examples/Validate-Date-Using-PHP.html
	 * @param    string    $date
	 * @param    string    format
	 * @return    bool
	 *
	 */
	static public function validateDate( $date, $format='YYYY-MM-DD')
	{
		switch( $format )
		{
			case 'YYYY/MM/DD':
			case 'YYYY-MM-DD':
				@list( $y, $m, $d ) = preg_split( '/[-\.\/ ]/', $date );
				break;

			case 'YYYY/DD/MM':
			case 'YYYY-DD-MM':
				@list( $y, $d, $m ) = preg_split( '/[-\.\/ ]/', $date );
				break;

			case 'DD-MM-YYYY':
			case 'DD/MM/YYYY':
				@list( $d, $m, $y ) = preg_split( '/[-\.\/ ]/', $date );
				break;

			case 'MM-DD-YYYY':
			case 'MM/DD/YYYY':
				@list( $m, $d, $y ) = preg_split( '/[-\.\/ ]/', $date );
				break;

			case 'YYYYMMDD':
				$y = substr( $date, 0, 4 );
				$m = substr( $date, 4, 2 );
				$d = substr( $date, 6, 2 );
				break;

			case 'YYYYDDMM':
				$y = substr( $date, 0, 4 );
				$d = substr( $date, 4, 2 );
				$m = substr( $date, 6, 2 );
				break;

			default:
				throw new \Exception( "Invalid Date Format" );
		}
		return @checkdate( $m, $d, $y );
	}
	
	
	/**
	 * Date format.
	 *
	 * @return string
	 */
	private static function getDefaultDateFormat() {
		return 'YYYY-MM-DD';
	}
	
}